/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

// 实现一个自己的Promise
// 1.首先构造函数应该接受一个执行器executor接收resolve以及reject
// 2.实现resolve以及reject方法，resolve：pending =》 fulfilled，reject：pending=》rejected
// 3.实现then方法，需要实现链式调用，参数补全，异步处理等
// 4.处理部分报错（executor，then）
// 5.实现promise方法(内部方法：catch, finally 静态方法: resolve, reject, all, race, allSettled, any)

const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const PENDING = 'pending';

class MyPromise {
    // 初始化状态为pending
    status = PENDING;
    // 存储 resolve 的值
    value = undefined;
    // 存储 reject 原因
    reason = undefined;
    // 存储成功回调
    successCallback = [];
    // 存储失败回调
    failCallback = [];

    constructor(executor) {
        try {
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e);
        }
    }

    resolve = (value) => {
        // 修改promise当前状态为fulfilled
        if (this.status !== PENDING) return;
        this.status = FULFILLED;
        this.value = value;
        while(this.successCallback.length) {
            this.successCallback.shift()();
        }
    };

    reject = (reason) => {
        // 修改promise当前状态为rejected
        if (this.status !== PENDING) return;
        this.status = REJECTED;
        this.reason = reason;
        while(this.failCallback.length) {
            this.failCallback.shift()();
        }
    };

    then = (successCallback, failCallback) => {
        // 处理参数补全情况
        // instanceOf Function是为了解决输入不是省略而不是函数的问题，不是函数就需要进行参数补全
        successCallback = successCallback instanceof Function ? successCallback : value => value;
        failCallback = failCallback instanceof Function ? failCallback : reason => { throw reason };

        if (this.status === FULFILLED) {
            const promise2 = new MyPromise((resolve, reject) => {
                // setTimeout为了异步获取到promise2
                setTimeout(() => {
                    try {
                        const x = successCallback(this.value);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            });

            return promise2;
        } else if (this.status === REJECTED) {
            const promise2 = new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        const x = failCallback(this.reason);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            });

            return promise2;
        } else {
            this.successCallback.push(() => {
                const promise2 = new MyPromise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            const x = successCallback(this.value);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });

                return promise2;
            });
            this.failCallback.push(() => {
                const promise2 = new MyPromise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            const x = failCallback(this.reason);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });

                return promise2;
            });
        }
    };

    static resolve(value) {
        // 根据promise特性，resolve接受promise直接返回
        if (value instanceof MyPromise) return value;
        return new MyPromise((resolve, reject) => {
            resolve(value);
        });
    }

    static reject(value) {
        // 根据promise特性，reject接受promise直接返回
        if (value instanceof MyPromise) return value;
        return new MyPromise((resolve, reject) => {
            reject(value);
        })
    }

    // 对一组数据进行依次处理，数组项可以为普通值或者promise，返回的是这组数据的处理值数组
    static all(array) {
        let result = [];
        let index = 0;
        return new MyPromise((resolve, reject) => {
            function addData(i, data) {
                result[i] = data;
                index++;
                // 在所有异步操作完成后resolve
                if (index === array.length) {
                    resolve(result);
                }
            }

            for(let i = 0; i < array.length; i++) {
                if (array[i] instanceof MyPromise) {
                    // 如果数组项是一个promise，那么执行并返回添加到结果数组中，一个失败则直接出发reject
                    array[i].then(value => addData(i, value), reason => reject(reason));
                } else {
                    // 普通值直添加
                    addData(i, array[i])
                }
            }
        });
    }

    // race方法，对一组数据进行依次处理，数组项可以为普通值或者promise，返回的是这组数据最先完成的值
    // 竞速的就是利用promise到了resolve/reject,状态即定值来实现，不可变
    static race(array) {
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < array.length; i++) {
                if (array[i] instanceof MyPromise) {
                    array[i].then(value => resolve(value), reason => reject(reason))
                } else {
                    resolve(array[i]);
                }
            }
        });

    }

    // allSettled方法，对一组数据进行处理,数组项可以为普通值或者promise，返回的是一个数组，每一个元素是一个对象
    // 对象具有2个属性：
    // status：fulfilled rejected 标识当前这个值的状态
    // value：当前的返回值 reason：当前reject原因
    // 实现，与all类似，但是需要额外保存状态
    static allSettled(array) {
        let result = [];
        let index = 0;
        return new MyPromise((resolve, reject) => {
            function addData(i ,data) {
                result[i] = data;
                index++;
                if (index === array.length) {
                    resolve(result);
                }
            }

            for (let i = 0;i < array.length;i++) {
                if (array[i] instanceof MyPromise) {
                    array[i].then(value => {
                        addData(i, {
                            status: FULFILLED,
                            value,
                        })
                    }, reason => {
                        addData(i, {
                            status: REJECTED,
                            reason,
                        })
                    });
                } else {
                    addData(i, {
                        status: FULFILLED,
                        value: array[i]
                    })
                }
            }
        });
    }

    // any方法，对一组数据进行处理,数组项可以为普通值或者promise，返回的是第一个fulfilled状态的promise
    // 类似race
    static any(array) {
        let flag = false;
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < array.length; i++) {
                if (array[i] instanceof MyPromise) {
                    array[i].then(value => {
                        flag = true;
                        resolve(value);
                    });
                } else {
                    flag = true;
                    resolve(array[i]);
                }
            }

            // 没有fulfilled的对象则抛错
            if (!flag) {
                reject(new AggregateError('No Promise in Promise.any was resolved'));
            }
        });
    }

    catch = (callback) => {
        // 直接传递错误回调即可
        return this.then(undefined, callback);
    };

    finally = (callback) => {
        // 处理callback异步情况，利用resolve
        return this.then((value) => {
            return Promise.resolve(callback()).then(() => value);
        }, (reason) => {
            return Promise.reject(callback()).then(undefined, () => { throw reason })
        });
    };
}

const resolvePromise = (x, promise2, resolve, reject) => {
    if (x === promise2) {
        // 发生了promise的循环调用，抛出类型错误
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
        return;
    }

    // 判断x是否是一个promise
    if (x instanceof MyPromise) {
        x.then(value => resolve(value), reason => reject(reason));
    } else {
        resolve(x);
    }
};

// test
// new MyPromise((resolve, reject) => {
//     resolve(1);
// }).then(value => {
//     console.log(value);
//     return value + 1;
// }).then(value => {
//     console.log(value);
// });

// test
// new MyPromise((resolve, reject) => {
//     reject(new Error('111'));
// }).then(value => {
//     console.log(value);
//     return value + 1;
// }, reason => {
//    console.log(reason);
// });

// test
// new MyPromise((resolve, reject) => {
//     reject(new Error('111'));
// }).then(value => {
//     console.log(value);
//     return value + 1;
// }).catch(reason => {
//     console.log(123, reason);
// });

// test
// new MyPromise((resolve, reject) => {
//     reject(new Error('111'));
// }).finally(() => {
//    return new MyPromise(resolve => {
//        console.log(111);
//        resolve();
//    });
// }).then(() => {}, (reason) => {
//     console.log(reason);
// });

// test
// const p1 = () => new MyPromise(((resolve, reject) => {
//     resolve(1);
// }));
//
// const p2 = () => new MyPromise(((resolve, reject) => {
//     resolve(2);
// }));
//
// MyPromise.all([ 1, 2, p1(), 3, p2()]).then(result => {
//     console.log(result);
// });

// test
// const p1 = () => new MyPromise(((resolve, reject) => {
//     setTimeout(() => {
//         resolve(1);
//     }, 600);
//
// }));
//
// const p2 = () => new MyPromise(((resolve, reject) => {
//     setTimeout(() => {
//         reject(1);
//     }, 100);
// }));
//
// MyPromise.race([p1(), p2(), 3]).then(result => {
//     console.log(1, result);
// }, reason => {
//     console.log(2, reason);
// });

// test
// const p1 = () => {
//     return new MyPromise((resolve, reject) => {
//         resolve(1);
//     });
// };
//
// const p2 = () => {
//     return new MyPromise((resolve, reject) => {
//         reject(1);
//     });
// };
//
// MyPromise.allSettled([1,2, p1(), 3, p2()]).then(value => {
//     console.log(value);
// });

// test
// const p1 = () => new MyPromise((resolve, reject) => {
//     resolve(1);;
// });
//
// const p2 = () => new MyPromise((resolve, reject) => {
//     reject(2);
// });
//
// MyPromise.any([p2()]).then(value => {
//     console.log(value);
// }).catch(e => {
//     console.log(e);
// });

